/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Copyright 2016, Blender Foundation.
 */

/** \file
 * \ingroup draw
 */

#include "MEM_guardedalloc.h"

#include "BLI_memiter.h"
#include "BLI_math.h"

#include "GPU_matrix.h"

#include "ED_screen.h"
#include "ED_view3d.h"

#include "UI_interface.h"

#include "WM_api.h"
#include "BLF_api.h"

#include "draw_manager_text.h"

typedef struct ViewCachedString {
  float vec[3];
  union {
    uchar ub[4];
    int pack;
  } col;
  short sco[2];
  short xoffs, yoffs;
  short flag;
  int str_len;

  /* str is allocated past the end */
  char str[0];
} ViewCachedString;

typedef struct DRWTextStore {
  BLI_memiter *cache_strings;
} DRWTextStore;

DRWTextStore *DRW_text_cache_create(void)
{
  DRWTextStore *dt = MEM_callocN(sizeof(*dt), __func__);
  dt->cache_strings = BLI_memiter_create(1 << 14); /* 16kb */
  return dt;
}

void DRW_text_cache_destroy(struct DRWTextStore *dt)
{
  BLI_memiter_destroy(dt->cache_strings);
  MEM_freeN(dt);
}

void DRW_text_cache_add(DRWTextStore *dt,
                        const float co[3],
                        const char *str,
                        const int str_len,
                        short xoffs,
                        short yoffs,
                        short flag,
                        const uchar col[4])
{
  int alloc_len;
  ViewCachedString *vos;

  if (flag & DRW_TEXT_CACHE_STRING_PTR) {
    BLI_assert(str_len == strlen(str));
    alloc_len = sizeof(void *);
  }
  else {
    alloc_len = str_len + 1;
  }

  vos = BLI_memiter_alloc(dt->cache_strings, sizeof(ViewCachedString) + alloc_len);

  copy_v3_v3(vos->vec, co);
  copy_v4_v4_uchar(vos->col.ub, col);
  vos->xoffs = xoffs;
  vos->yoffs = yoffs;
  vos->flag = flag;
  vos->str_len = str_len;

  /* allocate past the end */
  if (flag & DRW_TEXT_CACHE_STRING_PTR) {
    memcpy(vos->str, &str, alloc_len);
  }
  else {
    memcpy(vos->str, str, alloc_len);
  }
}

void DRW_text_cache_draw(DRWTextStore *dt, ARegion *ar)
{
  RegionView3D *rv3d = ar->regiondata;
  ViewCachedString *vos;
  int tot = 0;

  /* project first and test */
  BLI_memiter_handle it;
  BLI_memiter_iter_init(dt->cache_strings, &it);
  while ((vos = BLI_memiter_iter_step(&it))) {
    if (ED_view3d_project_short_ex(
            ar,
            (vos->flag & DRW_TEXT_CACHE_GLOBALSPACE) ? rv3d->persmat : rv3d->persmatob,
            (vos->flag & DRW_TEXT_CACHE_LOCALCLIP) != 0,
            vos->vec,
            vos->sco,
            V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_WIN | V3D_PROJ_TEST_CLIP_NEAR) ==
        V3D_PROJ_RET_OK) {
      tot++;
    }
    else {
      vos->sco[0] = IS_CLIPPED;
    }
  }

  if (tot) {
    int col_pack_prev = 0;

    if (rv3d->rflag & RV3D_CLIPPING) {
      ED_view3d_clipping_disable();
    }

    float original_proj[4][4];
    GPU_matrix_projection_get(original_proj);
    wmOrtho2_region_pixelspace(ar);

    GPU_matrix_push();
    GPU_matrix_identity_set();

    const int font_id = BLF_default();

    const uiStyle *style = UI_style_get();

    BLF_size(font_id, style->widget.points * U.pixelsize, U.dpi);

    BLI_memiter_iter_init(dt->cache_strings, &it);
    while ((vos = BLI_memiter_iter_step(&it))) {
      if (vos->sco[0] != IS_CLIPPED) {
        if (col_pack_prev != vos->col.pack) {
          BLF_color4ubv(font_id, vos->col.ub);
          col_pack_prev = vos->col.pack;
        }

        BLF_position(
            font_id, (float)(vos->sco[0] + vos->xoffs), (float)(vos->sco[1] + vos->yoffs), 2.0f);

        ((vos->flag & DRW_TEXT_CACHE_ASCII) ? BLF_draw_ascii : BLF_draw)(
            font_id,
            (vos->flag & DRW_TEXT_CACHE_STRING_PTR) ? *((const char **)vos->str) : vos->str,
            vos->str_len);
      }
    }

    GPU_matrix_pop();
    GPU_matrix_projection_set(original_proj);

    if (rv3d->rflag & RV3D_CLIPPING) {
      ED_view3d_clipping_enable();
    }
  }
}
